package com.ruoyi.quartz.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.quartz.domain.SysJob;

/**
 * 任务执行工具
 *
 * @author ruoyi
 */
public class JobInvokeUtil {
    /**
     * 执行方法
     * <p>根据系统任务配置的目标方法信息，通过反射调用对应的方法。</p>
     *
     * @param sysJob 系统任务对象，包含目标方法的调用信息
     * @throws Exception 当方法调用过程中发生异常时抛出
     */
    public static void invokeMethod(SysJob sysJob) throws Exception {
        String invokeTarget = sysJob.getInvokeTarget();
        String beanName = getBeanName(invokeTarget);
        String methodName = getMethodName(invokeTarget);
        List<Object[]> methodParams = getMethodParams(invokeTarget);

        if (!isValidClassName(beanName)) {
            // 从Spring容器中获取Bean实例
            Object bean = SpringUtils.getBean(beanName);
            invokeMethod(bean, methodName, methodParams);
        } else {
            // 通过类名创建新的实例
            Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
            invokeMethod(bean, methodName, methodParams);
        }
    }

    /**
     * 调用指定对象的方法
     * <p>根据传入的对象、方法名和参数列表，使用反射机制调用对应的方法。</p>
     *
     * @param bean         目标对象实例
     * @param methodName   需要调用的方法名称
     * @param methodParams 方法参数列表，每个元素为一个包含参数值和类型的数组
     * @throws NoSuchMethodException     如果找不到匹配的方法
     * @throws SecurityException         如果访问方法受到安全管理器限制
     * @throws IllegalAccessException    如果无法访问该方法
     * @throws IllegalArgumentException  如果参数不合法
     * @throws InvocationTargetException 如果方法调用过程中抛出异常
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) {
            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));
        } else {
            Method method = bean.getClass().getMethod(methodName);
            method.invoke(bean);
        }
    }

    /**
     * 校验字符串是否为合法的类全限定名
     * <p>判断给定字符串是否包含多个点号（至少两个），用于区分Spring Bean名称与类全限定名。</p>
     *
     * @param invokeTarget 待校验的目标字符串
     * @return true表示是类全限定名；false表示是Spring Bean名称
     */
    public static boolean isValidClassName(String invokeTarget) {
        return StringUtils.countMatches(invokeTarget, ".") > 1;
    }

    /**
     * 提取目标字符串中的Bean名称部分
     * <p>从格式如 "beanName.methodName(param)" 的字符串中提取出Bean名称。</p>
     *
     * @param invokeTarget 包含Bean名称和方法信息的目标字符串
     * @return 提取出的Bean名称
     */
    public static String getBeanName(String invokeTarget) {
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringBeforeLast(beanName, ".");
    }

    /**
     * 提取目标字符串中的方法名称部分
     * <p>从格式如 "beanName.methodName(param)" 的字符串中提取出方法名称。</p>
     *
     * @param invokeTarget 包含Bean名称和方法信息的目标字符串
     * @return 提取出的方法名称
     */
    public static String getMethodName(String invokeTarget) {
        String methodName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringAfterLast(methodName, ".");
    }

    /**
     * 解析目标字符串中的方法参数列表
     * <p>从格式如 "beanName.methodName('str', 123)" 的字符串中解析出参数值及其类型。</p>
     *
     * @param invokeTarget 包含方法调用信息的目标字符串
     * @return 参数列表，每个元素是一个包含参数值和类型的数组
     */
    public static List<Object[]> getMethodParams(String invokeTarget) {
        String methodStr = StringUtils.substringBetweenLast(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr)) {
            return null;
        }
        // 使用正则表达式分割参数，避免在引号内的逗号被误分割
        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
        List<Object[]> classs = new LinkedList<>();
        for (int i = 0; i < methodParams.length; i++) {
            String str = StringUtils.trimToEmpty(methodParams[i]);
            // String字符串类型，以'或"开头
            if (StringUtils.startsWithAny(str, "'", "\"")) {
                classs.add(new Object[]{StringUtils.substring(str, 1, str.length() - 1), String.class});
            }
            // boolean布尔类型，等于true或者false
            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) {
                classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
            }
            // long长整形，以L结尾
            else if (StringUtils.endsWith(str, "L")) {
                classs.add(new Object[]{Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class});
            }
            // double浮点类型，以D结尾
            else if (StringUtils.endsWith(str, "D")) {
                classs.add(new Object[]{Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class});
            }
            // 其他类型归类为整形
            else {
                classs.add(new Object[]{Integer.valueOf(str), Integer.class});
            }
        }
        return classs;
    }

    /**
     * 获取方法参数的类型数组
     * <p>将参数列表中的类型信息提取出来组成Class数组。</p>
     *
     * @param methodParams 参数列表，每个元素是一个包含参数值和类型的数组
     * @return 参数类型数组
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
        Class<?>[] classs = new Class<?>[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams) {
            classs[index] = (Class<?>) os[1];
            index++;
        }
        return classs;
    }

    /**
     * 获取方法参数的值数组
     * <p>将参数列表中的值信息提取出来组成Object数组。</p>
     *
     * @param methodParams 参数列表，每个元素是一个包含参数值和类型的数组
     * @return 参数值数组
     */
    public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
        Object[] classs = new Object[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams) {
            classs[index] = (Object) os[0];
            index++;
        }
        return classs;
    }
}
